package com.thinvent.recommend.manager.manager.impl;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.core.BulkRequest;
import co.elastic.clients.elasticsearch.core.BulkResponse;
import co.elastic.clients.elasticsearch.core.DeleteByQueryRequest;
import co.elastic.clients.elasticsearch.core.DeleteByQueryResponse;
import co.elastic.clients.elasticsearch.core.GetRequest;
import co.elastic.clients.elasticsearch.core.GetResponse;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.bulk.BulkOperation;
import co.elastic.clients.elasticsearch.core.bulk.BulkResponseItem;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.json.JsonData;
import com.thinvent.recommend.manager.dto.DocVectorDTO;
import com.thinvent.recommend.manager.manager.ESQueryManager;
import org.apache.http.util.EntityUtils;
import org.elasticsearch.client.Request;
import org.elasticsearch.client.Response;
import org.elasticsearch.client.RestClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * ES 向量查询管理器实现（含分页查询、按创建时间范围查询、单条查询、批量删除、清空、删除索引）
 */
@Component
public class ESQueryManagerImpl implements ESQueryManager {

    private static final Logger log = LoggerFactory.getLogger(ESQueryManagerImpl.class);

    @Autowired
    private ElasticsearchClient esClient;

    @Autowired
    private RestClient restClient;

    @Value("${elasticsearch.rest.indexName}")
    private String indexName;

    @Override
    public List<DocVectorDTO> searchAll(int page, int size) throws IOException {
        int from = (page - 1) * size;
        SearchRequest req = SearchRequest.of(s -> s
                .index(indexName)
                .from(from)
                .size(size)
                .query(q -> q.matchAll(m -> m))
        );
        return executeSearch(req);
    }

    @Override
    public List<DocVectorDTO> searchByDate(String startDate,
                                           String endDate,
                                           int page,
                                           int size) throws IOException {
        int from = (page - 1) * size;
        SearchRequest req = SearchRequest.of(s -> s
                .index(indexName)
                .from(from)
                .size(size)
                .query(q -> q.range(r -> r
                        .field("createTime")                    // 改为 createTime
                        .format("strict_date_optional_time")
                        .gte(JsonData.of(startDate))
                        .lte(JsonData.of(endDate))
                ))
        );
        return executeSearch(req);
    }

    @Override
    public DocVectorDTO getById(String docId) throws IOException {
        GetRequest getReq = GetRequest.of(g -> g
                .index(indexName)
                .id(docId)
        );
        GetResponse<Map> getResp = esClient.get(getReq, Map.class);
        if (!getResp.found()) {
            log.warn("未在索引 '{}' 中找到文档 ID: {}", indexName, docId);
            return null;
        }
        Map<String, Object> src = getResp.source();
        @SuppressWarnings("unchecked")
        Map<String, Double> vec = (Map<String, Double>) src.get("vec");
        String createTime = src.get("createTime") != null ? src.get("createTime").toString() : null;

        DocVectorDTO dto = new DocVectorDTO();
        dto.setDocId(getResp.id());
        dto.setVec(vec);
        dto.setCreateTime(createTime);

        log.info("根据 ID 查询到文档[{}]，createTime={}，向量维度={}",
                dto.getDocId(),
                dto.getCreateTime(),
                vec != null ? vec.size() : 0
        );
        return dto;
    }

    /**
     * 执行 ES 查询并转换成 DocVectorDTO 列表，包含 createTime 与 updateTime
     */
    private List<DocVectorDTO> executeSearch(SearchRequest req) throws IOException {
        // 1. 执行查询
        SearchResponse<Map> resp = esClient.search(req, Map.class);
        List<Hit<Map>> hits = resp.hits().hits();
        log.info("本次查询命中 {} 条记录", hits.size());

        List<DocVectorDTO> list = new ArrayList<>(hits.size());
        for (Hit<Map> hit : hits) {
            Map<String, Object> src = hit.source();

            // 2. 兼容地读取 vec 字段
            Object rawVec = src.get("vec");
            Map<String, Double> vecMap = new HashMap<>();
            if (rawVec instanceof Map) {
                @SuppressWarnings("unchecked")
                Map<String, Double> m = (Map<String, Double>) rawVec;
                vecMap.putAll(m);
            } else if (rawVec instanceof List) {
                @SuppressWarnings("unchecked")
                List<Object> listObj = (List<Object>) rawVec;
                for (int i = 0; i < listObj.size(); i++) {
                    Object o = listObj.get(i);
                    if (o instanceof Number) {
                        double v = ((Number) o).doubleValue();
                        if (v != 0.0) {
                            vecMap.put(String.valueOf(i), v);
                        }
                    }
                }
            }

            // 3. 取 createTime 与 updateTime
            String createTime = src.get("createTime") != null
                    ? src.get("createTime").toString()
                    : null;
            String updateTime = src.get("updateTime") != null
                    ? src.get("updateTime").toString()
                    : null;

            // 4. 填充 DTO
            DocVectorDTO dto = new DocVectorDTO();
            dto.setDocId(hit.id());
            dto.setVec(vecMap);
            dto.setCreateTime(createTime);
            dto.setUpdateTime(updateTime); // 新增设置

            list.add(dto);

            log.info("文档[{}] createTime={}，updateTime={}，向量维度={} (非零维度数)",
                    dto.getDocId(),
                    dto.getCreateTime(),
                    dto.getUpdateTime(),
                    vecMap.size());
        }
        return list;
    }

    @Override
    public void deleteByIds(List<String> docIds) throws IOException {
        if (docIds == null || docIds.isEmpty()) {
            return;
        }
        List<BulkOperation> ops = docIds.stream()
                .map(id -> BulkOperation.of(b -> b.delete(d -> d.index(indexName).id(id))))
                .collect(Collectors.toList());

        BulkResponse resp = esClient.bulk(BulkRequest.of(b -> b.operations(ops)));
        for (BulkResponseItem item : resp.items()) {
            if (item.error() != null) {
                log.error("删除文档[{}]失败：{}", item.id(), item.error().reason());
            } else {
                log.info("删除文档[{}]成功", item.id());
            }
        }
    }

    @Override
    public void clearAll() throws IOException {
        log.info("清空索引 [{}] 中所有文档", indexName);
        DeleteByQueryRequest req = DeleteByQueryRequest.of(d -> d
                .index(indexName)
                .query(q -> q.matchAll(ma -> ma))
        );
        DeleteByQueryResponse resp = esClient.deleteByQuery(req);
        log.info("清空完成，共删除 {} 条", resp.deleted());
    }

    @Override
    public void deleteIndex() throws IOException {
        log.info("开始删除 ES 索引 [{}] …", indexName);
        Request deleteReq = new Request("DELETE", "/" + indexName);
        Response resp = restClient.performRequest(deleteReq);
        int status = resp.getStatusLine().getStatusCode();
        if (status >= 200 && status < 300) {
            log.info("索引 [{}] 删除成功，HTTP {}", indexName, status);
        } else {
            String msg = "索引删除返回非 2xx：" + status;
            log.error(msg);
            throw new IOException(msg);
        }
    }

    @Override
    public String getIndexMapping() throws IOException {
        // 构造 GET /{index}/_mapping 请求
        Request req = new Request("GET", "/" + indexName + "/_mapping");
        Response resp = restClient.performRequest(req);
        // 直接返回原始 JSON
        return EntityUtils.toString(resp.getEntity(), "UTF-8");
    }
}
